package com.jdd.config;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.hash.Hashing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ReflectionUtils;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 注意，类中的日期类型需要添加注解来规范序列化格式！
 * @JsonFormat( pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
 */
@Configuration
@EnableCaching // 开启缓存支持
public class RedisConfig extends CachingConfigurerSupport {

	@Autowired
	private RedisConnectionFactory redisConnectionFactory;
	@Autowired
	private ApplicationContext applicationContext;

	/**
	 * @description 自定义的缓存key的生成策略 若想使用这个key
	 *              只需要讲注解上keyGenerator的值设置为keyGenerator即可</br>
	 * @return 自定义策略生成的key
	 */
	@Override
	@Bean
	public KeyGenerator keyGenerator() {
		return (target, method, params) -> {
			StringBuilder sb = new StringBuilder();
			sb.append(target.getClass().getName());
			sb.append(method.getDeclaringClass().getName());
			Arrays.stream(params).map(i -> {
				if (i instanceof HttpServletRequest) {
					return JSON.toJSONString(((HttpServletRequest) i).getParameterMap());
				} else {
					return i.toString();
				}
			}).forEach(sb::append);
			return Hashing.murmur3_128().hashString(sb.toString(), Charset.defaultCharset()).asLong();

		};
	}

	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
		RedisTemplate template = new RedisTemplate();
		template.setConnectionFactory(factory);
		StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
		// 设置key的序列化方式
		template.setKeySerializer(RedisSerializer.string());
		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		// 设置value序列化方式
		template.setValueSerializer(RedisSerializer.string());
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
		jackson2JsonRedisSerializer.setObjectMapper(om);
		template.setHashKeySerializer(RedisSerializer.string());
		template.setHashValueSerializer(RedisSerializer.string());
		template.afterPropertiesSet();
		return template;
	}
	@Override
	@Bean
	public CacheManager cacheManager() {
		RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
				.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JacksonRedisSerializer<>(Object.class)));
		return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
				.cacheDefaults(defaultCacheConfig)
				.withInitialCacheConfigurations(buildInitCaches())
				.build();
	}

	private Map<String, RedisCacheConfiguration> buildInitCaches() {
		HashMap<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
		Arrays.stream(applicationContext.getBeanNamesForType(Object.class))
				.map(applicationContext::getType).filter(Objects::nonNull)
				.forEach(clazz -> {
							ReflectionUtils.doWithMethods(clazz, method -> {
								ReflectionUtils.makeAccessible(method);
								Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
								if (Objects.nonNull(cacheable)) {
									for (String cache : cacheable.cacheNames()) {
										RedisSerializationContext.SerializationPair<Object> sp = RedisSerializationContext.SerializationPair
												.fromSerializer(new JacksonRedisSerializer<>(method.getGenericReturnType()));
										cacheConfigMap.put(cache, RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(sp));
									}
								}
							});
						}
				);
		return cacheConfigMap;
	}

}
